package com.gusto.engine.colfil.formula.impl;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import com.gusto.engine.colfil.Distance;
import com.gusto.engine.colfil.Evaluation;
import com.gusto.engine.colfil.formula.ItemCorrelation;
import com.gusto.engine.colfil.formula.UserCorrelation;

/**
 * <p>Implementation of the Cosine measure similarity.<br/>
 * Properties : 
 * <ul>
 * 	<li>Considers all the data, not only the common.
 *  <li>Not normalized.
 * </ul>
 * </p>
 * 
 * @author amokrane.belloui@gmail.com
 *
 */
public class CosineMeasureCorrelation implements ItemCorrelation, UserCorrelation {
	
	private Logger log = Logger.getLogger(getClass());
	
	private List<? extends Evaluation> createIfNull(List<? extends Evaluation> evals) {
		if (evals == null) {
			evals = new ArrayList<Evaluation>();
		}
		return evals;
	}
	
	private double calculateCorrelation(double sumXY, double sumX2, double sumY2) {
		double normX = Math.sqrt(sumX2);
		double normY = Math.sqrt(sumY2);
		double denominator = normX * normY;
		if (denominator == 0.0) {
			return Double.NaN;
		}
		return sumXY / denominator;
	}
	
	public Distance userCorrelation(long user1, long user2, List<? extends Evaluation> evals1, List<? extends Evaluation> evals2) {
		log.debug("Calculating User correlation " + user1 + " " + user2);
		
		evals1 = createIfNull(evals1);
		evals2 = createIfNull(evals2);
		
		Double totalSquare1 = 0.0;
		Double totalSquare2 = 0.0;
		Double total = 0.0;
		Integer count = 0;
		
		for (Evaluation e1 : evals1) {
			Double val1 = e1.getValue();
			totalSquare1 += val1 * val1;
		}
		for (Evaluation e2 : evals2) {
			Double val2 = e2.getValue();
			totalSquare2 += val2 * val2;
		}
		
		for (Evaluation e1 : evals1) {
			for (Evaluation e2 : evals2) {
				if (e1.getItemId() == e2.getItemId()) {
					Double val1 = e1.getValue();
					Double val2 = e2.getValue();
					total += val1 * val2;
					count++;
				}
			}
		}
		
		log.debug("Total  " + total + " | Sq1 " + totalSquare1 + " | Sq2 " + totalSquare2);
		
		Distance dist = new Distance();
		dist.setId1(user1);
		dist.setId2(user2);
		dist.setCount(count);
		dist.setDistance(calculateCorrelation(total, totalSquare1, totalSquare2));
		
		log.info("Calculating User correlation " + user1 + " " + user2 + " => " + dist);
		return dist;
	}
	
	public Distance itemCorrelation(long item1, long item2, List<? extends Evaluation> evals1, List<? extends Evaluation> evals2) {
		log.debug("Calculating Item correlation " + item1 + " " + item2);
		
		evals1 = createIfNull(evals1);
		evals2 = createIfNull(evals2);
		
		Double totalSquare1 = 0.0;
		Double totalSquare2 = 0.0;
		Double total = 0.0;
		Integer count = 0;
		
		for (Evaluation e1 : evals1) {
			Double val1 = e1.getValue();
			totalSquare1 += val1 * val1;
		}
		for (Evaluation e2 : evals2) {
			Double val2 = e2.getValue();
			totalSquare2 += val2 * val2;
		}
		
		for (Evaluation e1 : evals1) {
			for (Evaluation e2 : evals2) {
				if (e1.getUserId() == e2.getUserId()) {
					Double val1 = e1.getValue();
					Double val2 = e2.getValue();
					total += val1 * val2;
					count++;
				}
			}
		}
		
		log.debug("Total  " + total + " | Sq1 " + totalSquare1 + " | Sq2 " + totalSquare2);
		
		Distance dist = new Distance();
		dist.setId1(item1);
		dist.setId2(item2);
		dist.setCount(count);
		dist.setDistance(calculateCorrelation(total, totalSquare1, totalSquare2));
		
		log.info("Calculating Item correlation " + item1 + " " + item2 + " => " + dist);
		return dist;
	}
	
}
